export class Watcher {
  constructor(vm, exportFn, cb, options) {
    this.vm = vm;
    // 新增判断deep属性
    if (options) {
      this.deep = !options.deep
    } else {
      this.deep = false
    }
    this.deps = []  //存储订阅过的dep
    this.depIds = new Set()   //存储订阅过dep的id
    // 执行this.getter。可以读取到data.a.b.c的内容
    if (typeof exportFn == 'function') {
      // 如果是函数直接给getter   不是的话就读取keypath中数据。
      // 当exportFn是函数时，Watch会观察函数中所有用到的vue实例中的响应式数据。函数从vue实例读取了两个响应式数据，那么watcher会观察这两个数据的变化。
      this.getter = exportFn
    } else {
      this.getter = parsePath(exportFn);
    }
    this.cb = cb;
    this.value = this.get();
  }
  get() {
    window.target = this;
    let value = this.getter.call(this.vm, this.vm);
    // 新增
    if (this.deep) {
      traverse(value)
    }
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
  // 记录订阅过哪些dep
  addDep(dep) {
    const id = dep.id;
    if (!this.deps.has(id)) {
      this.depIds.push(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }
  /**
   * 从所有依赖项的dep列表中将自己移除
   */
  teardown() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].removeSub(this);
    }
  }
}

// 配置了deep属性，需要在window.target= undefined之前调用traverse函数来处理deep的逻辑
// 需要递归val中的所有子值来触发他们依赖收集的功能
const seenObjects = new Set()
function traverse(val) {
  _traverse(val, seenObjects);
  seenObjects.clear();
}

function _traverse(val, seen) {
  let i, keys;
  const isA = Array.isArray(val)
  // 不是array  不是object  已经冻结   直接返回
  if ((!isA && isObject(val)) || Object.isFrozen(val)) {
    return
  }
  if (val.__proto__) {
    // 拿到val的dep.id，用这个id来保证不会重复手机依赖
    const depId = val.__proto__.dep.id;
    if (seen.has(depId)) {
      return
    }
    seen.add(depId);
  }
  if (isA) {
    i = val.length;
    // 是数组，则循环数组将数组中的每一项递归调用_traverse
    while (i--) {
      _traverse(val[i], seen);
    }
  } else {
    // 是object 循环object中所有key
    keys = Object.keys(val);
    i = keys.length;
    while (i--) {
      _traverse(val[keys[i]], seen);
    }
  }
}